__author__ = 'Muscle'



import traceback
from mui.InternalUse.event import EventDispatcher
from mui.InternalUse.clock import Action
import mui.InternalUse.clock as cock
import time
import weakref


def getSet(setobj, setprop, getobj, getprop, cvt=None):
    """
    This function will execute:
        setobj.setprop = getobj.getprop
    Example:
    <pre>
        >>> getSet(setobj, "setprop", getobj, "getrop")
    </pre>
    """
    v = getattr(getobj, getprop)
    if cvt:
        v = cvt(v)
    setattr(setobj, setprop, v)

class IReceipt(object):
    def remove(self):
        pass

class ChangeEventReceipt(IReceipt):
    """
    used for remove the change event binding
    """
    def __init__(self, evtdispatcher, evtName, act):
        self.evtdispatcher = weakref.ref(evtdispatcher)
        self.evtName        = evtName
        self.act     = act

    def remove(self):
        if self.evtdispatcher:
            self.evtdispatcher().removeChangeEvent(self.evtName, self.act)

        self.evtdispatcher = None
        self.evtName = None
        self.act = None

class BindReceipt(IReceipt):
    """
    used for remove the event binding
    """
    def __init__(self, evtdispatcher, evtName, func):
        self.evtdispatcher = weakref.ref(evtdispatcher)
        self.evtName        = evtName
        self.func     = func

    def remove(self):
        if self.evtdispatcher:
            self.evtdispatcher().remove_handler(self.evtName, self.func)

        self.evtdispatcher = None
        self.evtName = None
        self.func = None


class MUIEventDispatcher(EventDispatcher):
    def __init__(self):
        super(MUIEventDispatcher, self).__init__()
        self._propChangeEvents = {} # first: postEvent, second: direct call
        self._eventReceipts = []

    def __del__(self):
        super(MUIEventDispatcher, self).__del__()
        self.clear()

    def __setattr__(self, name, v):
        """
        check if name is a alias, if so, map the change to the target
        """
        evtList = self.__dict__.get('_propChangeEvents')
        if not evtList:
            object.__setattr__(self, name, v)
            return

        pname = '$'+ name
        pre = pname in evtList
        post = name in evtList
        if post or pre:
            notthesame = False
            if hasattr(self, name):
                org = getattr(self, name)
                notthesame = (org != v)
            else:
                notthesame = True

            if notthesame and pre:
                for act in evtList[pname]:
                    act.call()

            object.__setattr__(self, name, v)

            if notthesame and post:
                for act in evtList[name]:
                    act.call()
        else:
            object.__setattr__(self, name, v)

    def clear(self):
        super(MUIEventDispatcher, self).clear()
        for receipts in self._eventReceipts:
            receipts.remove()
        self._eventReceipts = []

    def changeEvent(self, name, func, **kwargs):
        """
        handling property change
        @kwparam postevent: pack the function as Action or PostAction
        @type postevent: boolean
        @kwparam sync: if call the function immediately
        @type sync: boolean
        @rtype: NotifyEvent
        """

        # pack func and arg to an Action
        actArgs = kwargs.get('actArgs', [])
        actKwargs = kwargs.get('actKwargs', {})
        act = Action(func, actArgs, actKwargs)

        if kwargs.get('postevent', True):
            act.bPostCall = True
        else:
            act.bPostCall = False
        self._propChangeEvents.setdefault(name, []).append(act)
      
        ret = ChangeEventReceipt(self, name, act)
        if kwargs.get('sync', True) and hasattr(self, name):
            act.call()
        return ret

    def removeChangeEvent(self, evtName, act):
        """Remove an Action from an event

        FYI, usually this method is not what you want. You can call unbind(), unbindCmd(), or event.remove().

        @param evtName: the event that have the action to be removed
        @param act: the Action to be removed
        @rtype: None
        """
        if self._propChangeEvents.has_key(evtName) and act in self._propChangeEvents[evtName]:
            self._propChangeEvents[evtName].remove(act)

        return True

    def autoRemove(self, evtReceipt):
        """
        add to evtReceipts list, so the remove event will be called in destructor.
        """
        self._eventReceipts.append(evtReceipt)
        return True

    def bind(self, evtName, func, *args, **kwargs):
        receipt = BindReceipt(self, evtName, func)
        self.append_handler(evtName, func, *args, **kwargs)
        return receipt

    def unbind(self, evtName, func):
        kwargs = {evtName:func}
        self.remove_handler(evtName, func)

    def bindData(self, name, tar, tar_name, converter=None, **argd):
        """Bind data with given direction and converter.

        If bi-direction, the initial value is copied from target to self.

        ex:
        self.bindData('width', btn, 'width', dir='=')
        self.bindData('width', btn, 'width', dir='<-')
        self.bindData('width', btn, 'width', dir='->')
        """
        binddir = argd.get('dir', '=')
        if binddir == '<-':
            # <-
            # self.name = tar.tar_name
            cvt_left = converter
            getSet( self, name, tar, tar_name, cvt_left)
        elif binddir == '->':
            # ->
            # tar.tar_name = self.name
            cvt_right = converter
            getSet( tar, tar_name, self, name, cvt_right)
        else:
            # =
            # self.name = tar.tar_name
            if converter:
                cvt_left, cvt_right = converter
            else:
                cvt_left, cvt_right = None, None
            getSet( self, name, tar, tar_name, cvt_left)

        events = []
        if binddir != '->':
            # self.name <- tar.tar_name
          
            e = tar.changeEvent(tar_name, getSet, actArgs = (self, name, tar, tar_name, cvt_left), postevent = False, sync =False)
            self.autoRemove( e )
            events.append( e )
        if binddir != '<-':
            # self.name -> tar.tar_name
            e = self.changeEvent(name, getSet, actArgs = (tar, tar_name, self, name, cvt_right),  postevent = False, sync = False)
            tar.autoRemove( e )
            events.append( e )
        return events


    def bindCmd(self, cmd, func, *args, **kwargs):
        return self.bind('<'+cmd+'>', func, *args, **kwargs)

    def unbindCmd(self, cmd, func):
        return self.unbind('<'+cmd+'>', func)

    def dispatch_cmd(self, cmd, *arg):
        return self._dispatch_cmd('<'+cmd+'>', *arg)

    def _dispatch_cmd(self, cmd, *arg):
        if self.dispatch_event(cmd, *arg):
            return True
        elif getattr(self, 'parent', None):
            return self.parent._dispatch_cmd(cmd, *arg)
        return False

    def invoke(self, eventType, *args):
        self.dispatch_event(eventType, *args)

    def invokeCmd(self, cmd, *arg):
        self.dispatch_cmd(cmd, *arg)
